﻿# x - liczba urli, ktora dostanie agent 1 od agenta 2
# qi - wielkosc kolejki agenta i
# upti - liczba urli przegladana przez agenta w czasie jednego tiku (aktualnie 5s)
#
# q1[liczba urli] upt1[liczba urli/tiki] -> L1[tiki] = q1/upt1
# q2 upt2 -> L2 = q2/upt2
#
# (q1-x)/upt1 = (q2+x)/upt2
# q1*upt2-x*upt2 = q2*upt1+x*upt1
# q1*upt2-q2*upt1 = x(upt1+upt2)
# (q1*upt2-q2*upt1)/(upt1+upt2) = x
#
# Tak mniej-wiecej wyglada zarzadzanie urlami z LB:
# pending -> zbior z urlami wyslanymi do innych
# rcvd (= rcvd_pending) -> slownik {url:od_kogo_dostalem} urli, ktore przyszly z LB
#
# send(urls):
# queue -= urls
# pending += urls
#
# rcv(urls):
# queue += urls
# rcvd[urls] = sender
#
# visit(url):
# if url in rcvd:
#   rcvd -= (url:sender)
#   send(confirm(url), sender)
#
# rcv_confirm(urls):
# pending -= urls
# if urls in rcvd:
#   rcvd -= (url:org_sender)
#   send(confirm(url), org_sender)
# else:
#   visited += urls
#   last_visited += len(urls)

import spade
from CrawlerAgent import CrawlerAgent, MyPeriodicBehaviour, MakeAgentList
from SimpleSyncSet import SimpleSyncSet
from SimpleSyncDict import SimpleSyncDict
from collections import deque
from threading import Lock


_show_function_calls = True


class RespondLoadQuery(spade.Behaviour.Behaviour):
  #zawsze odpowiadam na pytanie o qsize, upt
  # -> inform
    def _process(self):
      msg = None
      msg = self._receive()
      if msg:
        self.myAgent.add_stat_rcvd_msgs()
        if _show_function_calls:
          print '\t+RespondLoadQuery'
        sender = msg.getSender()
        rmsg = msg.createReply()
        rmsg.setPerformative('inform')
        load = str(self.myAgent.url_queue.qsize()) + '#' + str(self.myAgent.avg_urls_per_tick)
        rmsg.setContent(load)
        self.myAgent.add_stat_sent_msgs()
        self.myAgent.send(rmsg)
        if _show_function_calls:
          print '\t-RespondLoadQuery'


  ##################################################################
  ## LBCrawlerAgent                                               ##
  ##################################################################

class LBCrawlerAgent(CrawlerAgent): # agent z consistent hashing i LB
  class ReceiveLBUrlsBehav(spade.Behaviour.Behaviour):
    def _process(self):
      msg = None
      msg = self._receive()
      if msg:
        self.myAgent.add_stat_rcvd_msgs()
        if _show_function_calls:
          print '\t+ReceiveLBUrlsBehav'
        urls_s = msg.getContent()
##        print '#',msg,'#'
        urls = urls_s.split(' ')
        sender = msg.getSender()
        print 'I got urls:', len(urls)
        self.myAgent.add_stat_rcvd_urls(len(urls))
        for url in urls:
          self.myAgent.url_queue.put(url)
          self.myAgent.rcvd_handler(url, sender)
        if _show_function_calls:
          print '\t-ReceiveLBUrlsBehav'
  ##################################################################
  class ReceiveConfirmVisit(spade.Behaviour.Behaviour):
    def _process(self):
      msg = None
      msg = self._receive()
      if msg:
        if _show_function_calls:
          print '\t+ReceiveConfirmVisit'
        urls_s = msg.getContent()
        urls = urls_s.split(' ')
        perf = msg.getPerformative()
        if perf == 'confirm':
          self.myAgent.confirmed_visit_handler(urls)
        else:
          print '\t\t\t### FAIL ###', perf
        if _show_function_calls:
          print '\t-ReceiveConfirmVisit'
  ##################################################################
  class CalculateLoad(MyPeriodicBehaviour):
    def _onTick(self):
      if _show_function_calls:
        print '\t+CalculateLoad'
      new_visited = len(self.myAgent.visited_set)
      dvisited = new_visited - self.myAgent.last_visited
      self.myAgent.last_visited = new_visited
      self.myAgent.urls_per_tick_history.popleft()
      self.myAgent.urls_per_tick_history.append(dvisited)
      self.myAgent.avg_urls_per_tick = 1.*sum(self.myAgent.urls_per_tick_history)/len(self.myAgent.urls_per_tick_history)
      print self.myAgent.urls_per_tick_history
      print self.myAgent.avg_urls_per_tick
      if _show_function_calls:
        print '\t-CalculateLoad'
  ##################################################################
  class SaveStatsBehaviour(MyPeriodicBehaviour):
    def _onTick(self):
      if _show_function_calls:
        print '\t+SaveStatsBehaviour'
      self.myAgent.save_stats()
      if _show_function_calls:
        print '\t-SaveStatsBehaviour'
  ##################################################################
  def __init__(self, agentjid, password, port=5222, debug=[], p2p=False, num=1, fname=None):
    self.service_name = None
    self.set_lb_agents = None
    CrawlerAgent.__init__(self, agentjid, password, port, debug, p2p, num, fname)
  ##################################################################
  def add_stat_sent_msgs(self, n = 1):
    '''Zwiekszenie licznika wyslanych wiadomosci odnosnie load balancingu'''
    self.lock.acquire()
    self.sent_load += n
##    f = open("stat_sent_msgs_" + self.getName() + ".txt", "w")
##    f.write(str(self.sent_load))
##    f.close()
    self.lock.release()
  ##################################################################
  def add_stat_rcvd_msgs(self, n = 1):
    '''Zwiekszenie licznika odebranych wiadomosci odnosnie load balancingu'''
    self.lock.acquire()
    self.rcvd_load += n
##    f = open("stat_rcvd_msgs_" + self.getName() + ".txt", "w")
##    f.write(str(self.rcvd_load))
##    f.close()
    self.lock.release()
  ##################################################################
  def add_stat_sent_urls(self, n):
    '''Zwiekszenie licznika wyslanych adresow odnosnie load balancingu'''
    self.lock.acquire()
    self.sent_urls += n
##    f = open("stat_sent_urls_" + self.getName() + ".txt", "w")
##    f.write(str(self.sent_urls))
##    f.close()
    self.lock.release()
  ##################################################################
  def add_stat_rcvd_urls(self, n):
    '''Zwiekszenie licznika odebranych adresow odnosnie load balancingu'''
    self.lock.acquire()
    self.rcvd_urls += n
##    f = open("stat_rcvd_urls_" + self.getName() + ".txt", "w")
##    f.write(str(self.rcvd_urls))
##    f.close()
    self.lock.release()
  ##################################################################
  def save_stats(self):
    self.lock.acquire()
    my_name = self.getName()
    print '\tfile 1'
    f = open("stat_sent_msgs_" + my_name + ".txt", "w")
    f.write(str(self.sent_load))
    f.close()
    print '\tfile 2'
    f = open("stat_rcvd_msgs_" + my_name + ".txt", "w")
    f.write(str(self.rcvd_load))
    f.close()
    print '\tfile 3'
    f = open("stat_sent_urls_" + my_name + ".txt", "w")
    f.write(str(self.sent_urls))
    f.close()
    print '\tfile 4'
    f = open("stat_rcvd_urls_" + my_name + ".txt", "w")
    f.write(str(self.rcvd_urls))
    f.close()
    self.lock.release()
  ##################################################################
  def sent_handler(self, url):
    self.pending.add(url) # trzeba dodac do pending, zeby nie sprawdzac urla 2 razy (tu i w innym agencie)
  ##################################################################
  def rcvd_handler(self, url, sender):
    self.rcvd_pending[url] = sender # zapisujemy, co dostalismy z LB, zeby poinformowac potem, ze zostalo sprawdzone
  ##################################################################
  def visited_url_handler(self, url):
    if url in self.rcvd_pending:
      org_sender = self.rcvd_pending[url]
      del self.rcvd_pending[url]
      msg = spade.ACLMessage.ACLMessage()
      msg.setPerformative('confirm')
      msg.setOntology(self.service_name)
      msg.addReceiver(org_sender)
      msg.setContent(url)
      self.send(msg)
  ##################################################################
  def confirmed_visit_handler(self, urls):
    my_url_num = 0
    for url in urls:
      self.pending.discard(url)
      if url in self.rcvd_pending:
        self.visited_url_handler(url)
      else:
        self.visited.add(url)
        my_url_num += 1
    self.last_visited += my_url_num
  ##################################################################
  def send_urls(self, url_num, receiver):
    if url_num <= 0:
      return
    print 'sending', url_num, 'urls to', receiver.getName(), ', urls on queue', self.url_queue.qsize()
    urls = []
    for i in xrange(url_num):
      url = self.url_queue.get()
      urls.append(url)
      self.sent_handler(url)
    print 'got urls from queue'
    urls_s = ' '.join(urls)
    print 'creating msg'
    msg = spade.ACLMessage.ACLMessage()
    msg.setPerformative('inform')
    print 'performative done'
    msg.setOntology(self.transfer_service_name)
    print 'ontology done'
    msg.addReceiver(receiver)
    print 'rcvr done'
    print 'content len', len(urls_s)
    msg.setContent(urls_s) # w tej linii sie wiesza :/
    print 'content done'
    self.add_stat_sent_urls(url_num)
    self.add_stat_sent_msgs()
    self.send(msg)
  ##################################################################
  def _setup(self):
    self.pending = SimpleSyncSet() # adresy wyslane przez LB, ktore nie zostaly jeszcze sprawdzone
    self.rcvd_pending = SimpleSyncDict() # adresy otrzymane przez LB, ktore nie zostaly jeszcze sprawdzone
    self.last_visited = 0
    self.avg_urls_per_tick = 2
    history_time = 600
    history_items = 20
    self.urls_per_tick_history = deque([self.avg_urls_per_tick] * history_items)
    #synchronizacja aktualizacji statystyk
    self.lock = Lock()
    #Zliczanie operacji wysylania/odbierania Load Balancingu
    self.sent_load = 0
    self.rcvd_load = 0
    #Zliczanie wyslanych/odebranych urli Load Balancingu
    self.sent_urls = 0
    self.rcvd_urls = 0

    if self.service_name != None:
      b = MakeAgentList(self.behav_period, self.make_list_timestart, self.service_name, self.set_lb_agents)
      self.addBehaviour(b)

      template = spade.Behaviour.ACLTemplate()
      template.setPerformative('inform')
      template.setOntology(self.transfer_service_name)
      mt = spade.Behaviour.MessageTemplate(template)
      b = self.ReceiveLBUrlsBehav()
      self.addBehaviour(b, mt)

      template = spade.Behaviour.ACLTemplate()
      template.setPerformative('confirm')
      template.setOntology(self.service_name)
      mt = spade.Behaviour.MessageTemplate(template)
      b = self.ReceiveConfirmVisit()
      self.addBehaviour(b, mt)

      self.add_service(self.service_name)

    b = self.CalculateLoad(history_time/history_items, self.lb_timestart + 1.5)
    self.addBehaviour(b)

    b = self.SaveStatsBehaviour(self.behav_period, self.lb_timestart + 3)
    self.addBehaviour(b)

    CrawlerAgent._setup(self)
